// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 垃圾收集器：标记和扫描

package runtime

import (
	"internal/goarch"
	"internal/goexperiment"
	"runtime/internal/atomic"
	"runtime/internal/sys"
	"unsafe"
)

const (
	fixedRootFinalizers = iota
	fixedRootFreeGStacks
	fixedRootCount

	// rootBlockBytes是每个数据或
	// BSS根要扫描的字节数。
	rootBlockBytes = 256 << 10

	// maxObletBytes是一个对象在
	// 上扫描一次的最大字节数。更大的物体将被分成大小为
	// /的“小物体”，大多数都是这种大小。因为我们可以扫描1–2MB/ms，128KB边界
	// 以大约100µs的速度扫描抢占。
	// 
	// 这必须是>\u MaxSmallSize，以便对象基是
	// span基。
	maxObletBytes = 128 << 10

	// drainCheckThreshold指定在gcDrain中的自抢占检查之间要执行多少个工作单元。假设扫描
	// 速率为1 MB/ms，这是~100µs。在扫描循环中，较低的值具有较高的
	// 开销（调度程序检查可能会执行
	// 系统调用，因此其开销并不重要）。更高的值
	// 会降低系统对传入工作的响应。
	drainCheckThreshold = 100000

	// pagesPerSpanRoot表示一次从跨距根扫描多少页。用于特殊的根部标记。
	// 
	// 更高的值通过增加局部性来提高吞吐量，但
	// 增加标记操作的最小延迟。
	// 
	// 必须是pageInUse bitmap元素大小的倍数，并且
	// 还必须平均分配PageSparena。
	pagesPerSpanRoot = 512
)

// gcMarkRootPrepare将根扫描作业（堆栈、全局和
// 一些杂项）排队，并初始化扫描相关状态。世界必须被阻止。
func gcMarkRootPrepare() {
	assertWorldStopped()

	// 计算有多少数据和BSS根块。
	nBlocks := func(bytes uintptr) int {
		return int(divRoundUp(bytes, rootBlockBytes))
	}

	work.nDataRoots = 0
	work.nBSSRoots = 0

	// 扫描全局。
	for _, datap := range activeModules() {
		nDataRoots := nBlocks(datap.edata - datap.data)
		if nDataRoots > work.nDataRoots {
			work.nDataRoots = nDataRoots
		}
	}

	for _, datap := range activeModules() {
		nBSSRoots := nBlocks(datap.ebss - datap.bss)
		if nBSSRoots > work.nBSSRoots {
			work.nBSSRoots = nBSSRoots
		}
	}

	// 扫描跨度根以查找终结器特价。
	// 
	// 我们依赖addfinalizer来标记在根标记后获得
	// 终结器的对象。
	// 
	// 我们将扫描整个堆（在
	// 标记阶段开始时可用，即markArenas），以查看具有特殊功能的在用跨距。
	// 
	// 将作品分为多个领域，并进一步分为多个部分。
	// 
	// 将阿拉里纳斯快照为markArenas。此快照是安全的，因为allArenas 
	// 是仅附加的。
	mheap_.markArenas = mheap_.allArenas[:len(mheap_.allArenas):len(mheap_.allArenas)]
	work.nSpanRoots = len(mheap_.markArenas) * (pagesPerArena / pagesPerSpanRoot)

	// 扫描堆栈。
	// 
	// Gs可能会在这一点之后创建，但我们可以忽略它们，因为它们开始生命时没有任何根，所以
	// 没有什么可扫描的，它们在
	// 并发阶段创建的任何根都将被写入障碍捕获。
	work.stackRoots = allGsSnapshot()
	work.nStackRoots = len(work.stackRoots)

	work.markrootNext = 0
	work.markrootJobs = uint32(fixedRootCount + work.nDataRoots + work.nBSSRoots + work.nSpanRoots + work.nStackRoots)

	// 计算每个根类型的基本索引
	work.baseData = uint32(fixedRootCount)
	work.baseBSS = work.baseData + uint32(work.nDataRoots)
	work.baseSpans = work.baseBSS + uint32(work.nBSSRoots)
	work.baseStacks = work.baseSpans + uint32(work.nSpanRoots)
	work.baseEnd = work.baseStacks + uint32(work.nStackRoots)
}

// gcMarkRootCheck检查是否已扫描所有根。这是
// 纯粹用于调试。
func gcMarkRootCheck() {
	if work.markrootNext < work.markrootJobs {
		print(work.markrootNext, " of ", work.markrootJobs, " markroot jobs done\n")
		throw("left over markroot jobs")
	}

	// 检查堆栈是否已被扫描。
	// 
	// 我们只检查应该扫描的第一批NSTACKG。
	// 由于我们不关心较新的Gs（请参阅
	// gcMarkRootPrepare中的注释），因此不需要锁定。
	i := 0
	forEachGRace(func(gp *g) {
		if i >= work.nStackRoots {
			return
		}

		if !gp.gcscandone {
			println("gp", gp, "goid", gp.goid,
				"status", readgstatus(gp),
				"gcscandone", gp.gcscandone)
			throw("scan missed a g")
		}

		i++
	})
}

// 包含单个指针的分配的ptrmak。
var oneptrmask = [...]uint8{1}

// markroot扫描第i根。
// 
// 必须禁用抢占（因为这使用了gcWork）。
// 
// 返回操作产生的GC工作信用的金额。
// 如果flushBgCredit为真，则该信用也会被刷新到后台信用池中。
// 
// nowritebarrier是这里唯一的建议。
// 
// go:nowritebarrier 
func markroot(gcw *gcWork, i uint32, flushBgCredit bool) int64 {
	// 注意：如果您在此处添加案例，请同时更新heapdump。去：垃圾堆。
	var workDone int64
	var workCounter *atomic.Int64
	switch {
	case work.baseData <= i && i < work.baseBSS:
		workCounter = &gcController.globalsScanWork
		for _, datap := range activeModules() {
			workDone += markrootBlock(datap.data, datap.edata-datap.data, datap.gcdatamask.bytedata, gcw, int(i-work.baseData))
		}

	case work.baseBSS <= i && i < work.baseSpans:
		workCounter = &gcController.globalsScanWork
		for _, datap := range activeModules() {
			workDone += markrootBlock(datap.bss, datap.ebss-datap.bss, datap.gcbssmask.bytedata, gcw, int(i-work.baseBSS))
		}

	case i == fixedRootFinalizers:
		for fb := allfin; fb != nil; fb = fb.alllink {
			cnt := uintptr(atomic.Load(&fb.cnt))
			scanblock(uintptr(unsafe.Pointer(&fb.fin[0])), cnt*unsafe.Sizeof(fb.fin[0]), &finptrmask[0], gcw, nil)
		}

	case i == fixedRootFreeGStacks:
		// 切换到系统堆栈，这样我们就可以调用
		// stackfree。
		systemstack(markrootFreeGStacks)

	case work.baseSpans <= i && i < work.baseStacks:
		// 马克·姆斯潘。特辑
		markrootSpans(gcw, int(i-work.baseSpans))

	default:
		// 剩下的是扫描goroutine堆栈
		workCounter = &gcController.stackScanWork
		if i < work.baseStacks || work.baseEnd <= i {
			printlock()
			print("runtime: markroot index ", i, " not in stack roots range [", work.baseStacks, ", ", work.baseEnd, ")\n")
			throw("markroot: bad index")
		}
		gp := work.stackRoots[i-work.baseStacks]

		// 还记得我们第一次观察到G阻塞
		// 只需要以回溯方式输出
		status := readgstatus(gp) // 我们没有处于扫描状态
		if (status == _Gwaiting || status == _Gsyscall) && gp.waitsince == 0 {
			gp.waitsince = work.tstart
		}

		// 扫描堆栈必须在系统堆栈上进行，以防
		// 我们正在尝试扫描我们自己的堆栈。
		systemstack(func() {
			// 如果这是自扫描，请将用户G放入
			// /\u Gwaiting以防止自死锁。如果这是一个标记，它可能已经在等待中了
			// 工人，否则我们就要被解雇了。
			userG := getg().m.curg
			selfScan := gp == userG && readgstatus(userG) == _Grunning
			if selfScan {
				casgstatus(userG, _Grunning, _Gwaiting)
				userG.waitreason = waitReasonGarbageCollectionScan
			}

			// TODO:暂停（并旋转）直到gp 
			// 停止，这可能需要一段时间来运行goroutines。考虑在ABCFDG＊＊＊＊＊＊／／两个阶段中，第一个阶段是非阻塞的：ABCFDG＊＊＊＊＊＊＊/我们扫描堆栈，并要求运行ABCFDG＊＊＊＊＊＊＊＊＊＊＊＊/GOOTUTIN扫描自己；还有
			// 第二个街区。
			stopped := suspendG(gp)
			if stopped.dead {
				gp.gcscandone = true
				return
			}
			if gp.gcscandone {
				throw("g already scanned")
			}
			workDone += scanstack(gp, gcw)
			gp.gcscandone = true
			resumeG(stopped)

			if selfScan {
				casgstatus(userG, _Gwaiting, _Grunning)
			}
		})
	}
	if goexperiment.PacerRedesign {
		if workCounter != nil && workDone != 0 {
			workCounter.Add(workDone)
			if flushBgCredit {
				gcFlushBgCredit(workDone)
			}
		}
	}
	return workDone
}

// markrootBlock扫描内存块的第个碎片[b0，
// b0+n0），使用给定的指针掩码。
// 
// 返回已完成的工作量。
// 
// go:nowritebarrier 
func markrootBlock(b0, n0 uintptr, ptrmask0 *uint8, gcw *gcWork, shard int) int64 {
	if rootBlockBytes%(8*goarch.PtrSize) != 0 {
		// 这是在ptrmask0中选择字节偏移量所必需的。
		throw("rootBlockBytes must be a multiple of 8*ptrSize")
	}

	// 注意，如果b0接近地址空间的末尾，
	// 编写这些测试是为了避免任何可能的溢出。
	off := uintptr(shard) * rootBlockBytes
	if off >= n0 {
		return 0
	}
	b := b0 + off
	ptrmask := (*uint8)(add(unsafe.Pointer(ptrmask0), uintptr(shard)*(rootBlockBytes/(8*goarch.PtrSize))))
	n := uintptr(rootBlockBytes)
	if off+n > n0 {
		n = n0 - off
	}

	// 扫描此碎片。
	scanblock(b, n, ptrmask, gcw, nil)
	return int64(n)
}

// markrootFreeGStacks可以释放一堆堆死G。
// 
// 这不会释放Ps上缓存的死G堆栈，但有几个
// 缓存堆栈不是问题。
func markrootFreeGStacks() {
	// 获取带有堆栈的死G列表。
	lock(&sched.gFree.lock)
	list := sched.gFree.stack
	sched.gFree.stack = gList{}
	unlock(&sched.gFree.lock)
	if list.empty() {
		return
	}

	// 自由堆栈。
	q := gQueue{list.head, list.head}
	for gp := list.head.ptr(); gp != nil; gp = gp.schedlink.ptr() {
		stackfree(gp.stack)
		gp.stack.lo = 0
		gp.stack.hi = 0
		// 直接操作队列，因为Gs是
		// 已经以正确的方式链接。
		q.tail.set(gp)
	}

	// 将Gs放回免费列表。
	lock(&sched.gFree.lock)
	sched.gFree.noStack.pushAll(q)
	unlock(&sched.gFree.lock)
}

// markrootSpans为一个markArenas碎片标记根。
// 
// go:nowritebarrier 
func markrootSpans(gcw *gcWork, shard int) {
	// 带有终结器的对象有两个与GC相关的不变量：
	// 
	// 1）必须标记从对象可以访问的所有内容。
	// 这确保了当我们将对象传递给其终结器时，终结器可以到达的所有内容都将被保留。
	// 
	// 2）终结器特殊项（不在垃圾
	// 收集的堆中）是根。实际上，这意味着必须扫描fn 
	// 字段。
	sg := mheap_.sweepgen

	// 找到竞技场并为该碎片在该竞技场中建立页面索引。
	ai := mheap_.markArenas[shard/(pagesPerArena/pagesPerSpanRoot)]
	ha := mheap_.arenas[ai.l1()][ai.l2()]
	arenaPage := uint(uintptr(shard) * pagesPerSpanRoot % pagesPerArena)

	// 构造我们将迭代的位图切片。
	specialsbits := ha.pageSpecials[arenaPage/8:]
	specialsbits = specialsbits[:pagesPerSpanRoot/8]
	for i := range specialsbits {
		// 查找设置位，它对应于带有特殊值的跨距。
		specials := atomic.Load8(&specialsbits[i])
		if specials == 0 {
			continue
		}
		for j := uint(0); j < 8; j++ {
			if specials&(1<<j) == 0 {
				continue
			}
			// 查找此位的跨度。
			// 
			// 这个值保证为非零，因为有
			// 特殊值意味着跨度正在使用，而且由于我们目前正在标记
			// 我们可以确定，我们不必担心
			// 跨度被释放和重新使用。
			s := ha.spans[arenaPage+uint(i)*8+j]

			// 如果设置了specials位，则状态必须为MSPANINUUSE，因此
			// 检查是否正常。
			if state := s.state.get(); state != mSpanInUse {
				print("s.state = ", state, "\n")
				throw("non in-use span found with specials bit set")
			}
			// 检查此范围是否已扫描（可能已缓存或未缓存）。
			if !useCheckmark && !(s.sweepgen == sg || s.sweepgen == sg+3) {
				// sweepgen在非复选标记GC pass期间更新（+2）
				print("sweep ", s.sweepgen, " ", sg, "\n")
				throw("gc: unswept span")
			}

			// 锁定特殊项以防止特殊项在我们遍历时从列表中删除。
			lock(&s.speciallock)
			for sp := s.specials; sp != nil; sp = sp.next {
				if sp.kind != _KindSpecialFinalizer {
					continue
				}
				// 不要标记最终确定的对象，但要扫描它，这样我们就能保留它指向的所有内容。
				spf := (*specialfinalizer)(unsafe.Pointer(sp))
				// 可以为对象的内部字节“查找对象开头”设置终结器。
				p := s.base() + uintptr(spf.special.offset)/s.elemsize*s.elemsize

				// 标记所有可以从
				// 获取的对象（但*不是*对象本身或
				// 我们永远不会收集它）。
				scanobject(p, gcw)

				// 特殊本身就是一个根。
				scanblock(uintptr(unsafe.Pointer(&spf.fn)), goarch.PtrSize, &oneptrmask[0], gcw, nil)
			}
			unlock(&s.speciallock)
		}
	}
}

// gcAssistAlloc执行GC工作，使gp的辅助债务为正。
// gp必须是调用用户goroutine。
// 
// 必须在启用抢占的情况下调用此函数。
func gcAssistAlloc(gp *g) {
	// 在不可抢占的情况下不要提供帮助。这些是
	// 通常易碎，不允许辅助系统阻挡。
	if getg() == gp.m.g0 {
		return
	}
	if mp := getg().m; mp.locks > 0 || mp.preemptoff != "" {
		return
	}

	traced := false
retry:
	// 计算使
	// 平衡为正所需的扫描工作量。当所需工作量较低时，
	// 我们过度协助建立未来分配的信贷
	// 并分摊协助成本。
	assistWorkPerByte := gcController.assistWorkPerByte.Load()
	assistBytesPerWork := gcController.assistBytesPerWork.Load()
	debtBytes := -gp.gcAssistBytes
	scanWork := int64(assistWorkPerByte * float64(debtBytes))
	if scanWork < gcOverAssistWork {
		scanWork = gcOverAssistWork
		debtBytes = int64(assistBytesPerWork * float64(scanWork))
	}

	// 从后台GC的
	// 扫描信用。这很有意思，如果两个突变同时窃取，背景
	// 积分可能会降到0以下。这个
	// 只会导致盗窃失败，直到信用再次累积
	// 所以从长远来看这并不重要，但我们必须处理负面信用案例。
	bgScanCredit := atomic.Loadint64(&gcController.bgScanCredit)
	stolen := int64(0)
	if bgScanCredit > 0 {
		if bgScanCredit < scanWork {
			stolen = bgScanCredit
			gp.gcAssistBytes += 1 + int64(assistBytesPerWork*float64(stolen))
		} else {
			stolen = scanWork
			gp.gcAssistBytes += debtBytes
		}
		atomic.Xaddint64(&gcController.bgScanCredit, -stolen)

		scanWork -= stolen

		if scanWork == 0 {
			// 我们能够窃取我们
			// 所需的所有信贷。
			if traced {
				traceGCMarkAssistDone()
			}
			return
		}
	}

	if trace.enabled && !traced {
		traced = true
		traceGCMarkAssistStart()
	}

	// 执行辅助工作
	systemstack(func() {
		gcAssistAlloc1(gp, scanWork)
		// 用户堆栈可能已移动，因此在从systemstack返回之前，它不能触及
		// 其上的任何内容。
	})

	completed := gp.param != nil
	gp.param = nil
	if completed {
		gcMarkDone()
	}

	if gp.gcAssistBytes < 0 {
		// 我们无法窃取足够的信用或执行
		// 足够的工作来偿还债务。在让突变子分配
		// 更多之前，我们需要执行其中一项操作，以防止过度分配。
		// 
		// 如果这是因为我们被抢先，请重新安排
		// 并尝试更多。
		if gp.preempt {
			Gosched()
			goto retry
		}

		// 将此G添加到辅助队列并停车。当GC 
		// 有更多的后台信用时，它将满足排队的
		// 协助，然后刷新到全局信用池。
		// 
		// 请注意，当更多
		// 工作添加到工作列表中时，这不会被唤醒。理论是，反正没有足够的工作要做，所以我们可以让背景标记来处理可用的工作。
		if !gcParkAssist() {
			goto retry
		}

		// 此时，要么背景GC已满足
		// 此G的辅助债务，要么GC周期结束。
	}
	if traced {
		traceGCMarkAssistDone()
	}
}

// gcAssistAlloc1是在系统
// 堆栈上运行的gcAssistAlloc的一部分。这是一个单独的函数，可以更容易地看到
// 我们没有从用户堆栈中捕获任何内容，因为当我们使用此函数时，用户
// 堆栈可能会移动。
// 
// gcAssistAlloc1通过设置gp指示此辅助是否完成标记
// 阶段。参数为非零。这无法通过
// 堆栈进行通信，因为它可能会移动。
// 
// go:systemstack 
func gcAssistAlloc1(gp *g, scanWork int64) {
	// 清除指示此辅助完成
	// 标记阶段的标志。
	gp.param = nil

	if atomic.Load(&gcBlackenEnabled) == 0 {
		// 在malloc中的GCBlackEnabled检查与清除它的
		// 存储区竞争，但在每个malloc中的原子检查
		// 将对性能造成影响。
		// 相反，我们在不可抢占系统
		// 堆栈上重新检查它，以确定是否应该执行辅助。

		// GC已经完成，所以忽略任何剩余债务。
		gp.gcAssistBytes = 0
		return
	}
	// 跟踪在这项协助中花费的时间。因为我们在
	// 系统堆栈上，这是不可抢占的，所以我们可以测量开始和结束时间。
	startTime := nanotime()

	decnwait := atomic.Xadd(&work.nwait, -1)
	if decnwait == work.nproc {
		println("runtime: work.nwait =", decnwait, "work.nproc=", work.nproc)
		throw("nwait > work.nprocs")
	}

	// gcDrainN要求调用者是可抢占的。
	casgstatus(gp, _Grunning, _Gwaiting)
	gp.waitreason = waitReasonGCAssistMarking

	// 首先清空自己缓存的工作，希望它对缓存更加友好。
	gcw := &getg().m.p.ptr().gcw
	workDone := gcDrainN(gcw, scanWork)

	casgstatus(gp, _Gwaiting, _Grunning)

	// 记录我们做了这么多扫描工作。
	// 
	// 退出
	// 此扫描工作计算的辅助信用的字节数。“1+”是穷人的
	// 总括，以确保即使
	// 辅助字节数非常低，也能增加信用。
	assistBytesPerWork := gcController.assistBytesPerWork.Load()
	gp.gcAssistBytes += 1 + int64(assistBytesPerWork*float64(workDone))

	// 如果这是最后一个工人，我们没有工作了，
	// 表示一个完成点。
	incnwait := atomic.Xadd(&work.nwait, +1)
	if incnwait > work.nproc {
		println("runtime: work.nwait=", incnwait,
			"work.nproc=", work.nproc)
		throw("work.nwait > work.nproc")
	}

	if incnwait == work.nproc && !gcMarkWorkAvailable(nil) {
		// 这已达到后台完成点。设置
		// gp。param设置为非nil值以指示此情况。它
		// 不管我们将它设置为什么（它必须是
		// 一个有效的指针）。
		gp.param = unsafe.Pointer(gp)
	}
	duration := nanotime() - startTime
	_p_ := gp.m.p.ptr()
	_p_.gcAssistTime += duration
	if _p_.gcAssistTime > gcAssistTimeSlack {
		atomic.Xaddint64(&gcController.assistTime, _p_.gcAssistTime)
		_p_.gcAssistTime = 0
	}
}

// gcWakeAllAssists唤醒所有当前阻止的协助。在GC循环结束时使用
// 。gcBlackenEnabled必须为false，以防止
// 新助攻在此点之后进入睡眠状态。
func gcWakeAllAssists() {
	lock(&work.assistQueue.lock)
	list := work.assistQueue.q.popList()
	injectglist(&list)
	unlock(&work.assistQueue.lock)
}

// gcParkAssist将当前的goroutine放入辅助队列并停车。
// 
// gcParkAssist报告现在是否满足了协助要求。如果
// 返回false，则调用者必须重试协助。
func gcParkAssist() bool {
	lock(&work.assistQueue.lock)
	// 如果GC循环在我们获取锁时结束，
	// 退出辅助。当我们持有
	// 锁时，循环无法结束。
	if atomic.Load(&gcBlackenEnabled) == 0 {
		unlock(&work.assistQueue.lock)
		return true
	}

	gp := getg()
	oldList := work.assistQueue.q
	work.assistQueue.q.pushBack(gp)

	// 重新检查背景信用，现在此G已在队列中，但仍可以退出。这避免了一场
	// 竞赛，以防背景标记自我们上面检查以来刷新了更多
	// 积分。
	if atomic.Loadint64(&gcController.bgScanCredit) > 0 {
		work.assistQueue.q = oldList
		if oldList.tail != 0 {
			oldList.tail.ptr().schedlink.set(nil)
		}
		unlock(&work.assistQueue.lock)
		return false
	}
	// 公园。
	goparkunlock(&work.assistQueue.lock, waitReasonGCAssistWait, traceEvGoBlockGC, 2)
	return true
}

// gcFlushBgCredit刷新后台扫描工作的扫描工作单元
// credit。这首先满足了
// /工作中的阻塞协助。assistQueue，然后将所有剩余的积分刷新到
// gcController。谢谢。
// 
// 不允许使用写屏障，因为gcDrain在
// 它已确保所有工作都已耗尽，并且必须保留该
// 条件。
// 
// go:nowritebarrierrec 
func gcFlushBgCredit(scanWork int64) {
	if work.assistQueue.q.empty() {
		// Fast path；没有阻挡助攻。这里有一个
		// 小窗口，在这个窗口中，助手可以将自己添加到
		// 阻塞的队列和停车场。如果发生这种情况，我们将在下一次冲水时得到它。
		atomic.Xaddint64(&gcController.bgScanCredit, scanWork)
		return
	}

	assistBytesPerWork := gcController.assistBytesPerWork.Load()
	scanBytes := int64(float64(scanWork) * assistBytesPerWork)

	lock(&work.assistQueue.lock)
	for !work.assistQueue.q.empty() && scanBytes > 0 {
		gp := work.assistQueue.q.pop()
		// 请注意gp。gcAssistBytes为负值，因为gp 
		// 负债。仔细考虑下面的标志。
		if scanBytes+gp.gcAssistBytes >= 0 {
			// 偿还全部债务。
			scanBytes += gp.gcAssistBytes
			gp.gcAssistBytes = 0
			// 重要的是我们*不要*把gp放在
			// 下一步运行。否则，用户
			// 代码可能会利用GC工作者的高
			// 调度程序优先级，使其自身始终在其他Goroutine之前运行
			// 并始终在由GC启动的
			// 新量程中运行。
			ready(gp, 0, false)
		} else {
			// 部分满足此协助。
			gp.gcAssistBytes += scanBytes
			scanBytes = 0
			// 作为一种启发，我们将此辅助移动到队列后面的
			// 以便大辅助
			// 不会阻塞辅助队列，而
			// 大大延迟小辅助。
			work.assistQueue.q.pushBack(gp)
			break
		}
	}

	if scanBytes > 0 {
		// 从扫描字节转换回工作字节。
		assistWorkPerByte := gcController.assistWorkPerByte.Load()
		scanWork = int64(float64(scanBytes) * assistWorkPerByte)
		atomic.Xaddint64(&gcController.bgScanCredit, scanWork)
	}
	unlock(&work.assistQueue.lock)
}

// 扫描堆栈扫描gp的堆栈，将堆栈上找到的所有指针变灰。
// 
// 用于实验。PacerDesign:
// 返回执行的扫描工作量，但不更新
// gcController。堆叠扫描或刷新任何信用卡。此函数生成的任何后台信用都应由其调用方刷新。scanstack本身无法安全刷新
// 因为它可能会导致试图唤醒刚刚扫描过
// 的goroutine，从而导致自死锁。
// 
// 如果安全的话，scanstack也会收缩堆栈。如果
// 不是，它将为下一个同步安全
// 点安排堆栈收缩。
// 
// scanstack被标记为go:systemstack，因为它在使用workbuf时不能被抢占。
// 
// go:nowritebarrier 
// go:systemstack 
func scanstack(gp *g, gcw *gcWork) int64 {
	if readgstatus(gp)&_Gscan == 0 {
		print("runtime:scanstack: gp=", gp, ", goid=", gp.goid, ", gp->atomicstatus=", hex(readgstatus(gp)), "\n")
		throw("scanstack - bad status")
	}

	switch readgstatus(gp) &^ _Gscan {
	default:
		print("runtime: gp=", gp, ", goid=", gp.goid, ", gp->atomicstatus=", readgstatus(gp), "\n")
		throw("mark - bad status")
	case _Gdead:
		return 0
	case _Grunning:
		print("runtime: gp=", gp, ", goid=", gp.goid, ", gp->atomicstatus=", readgstatus(gp), "\n")
		throw("scanstack: goroutine not stopped")
	case _Grunnable, _Gsyscall, _Gwaiting:
		// ok 
	}

	if gp == getg() {
		throw("can't scan our own stack")
	}

	// stackSize是我们将报告的工作量。
	// 
	// 我们报告的堆栈总大小比我们扫描的要大，
	// 因为这个数字需要与gcControllerState的
	// stackScan和scannableStackSize字段对齐。
	// 
	// 有关更多信息，请参阅这些字段的文档。
	stackSize := gp.stack.hi - gp.stack.lo

	if isShrinkStackSafe(gp) {
		// 如果使用的堆栈不多，请缩小堆栈。
		shrinkstack(gp)
	} else {
		// 否则，在下一个同步安全点收缩堆栈。
		gp.preemptShrink = true
	}

	var state stackScanState
	state.stack = gp.stack

	if stackTraceDebug {
		println("stack trace goroutine", gp.goid)
	}

	if debugScanConservative && gp.asyncSafePoint {
		print("scanning async preempted goroutine ", gp.goid, " stack [", hex(gp.stack.lo), ",", hex(gp.stack.hi), ")\n")
	}

	// 扫描保存的上下文寄存器。这实际上是一个活动的
	// 寄存器，在
	// 寄存器和sched之间来回移动。没有写障碍的ctxt。
	if gp.sched.ctxt != nil {
		scanblock(uintptr(unsafe.Pointer(&gp.sched.ctxt)), goarch.PtrSize, &oneptrmask[0], gcw, &state)
	}

	// 扫描堆栈。累积堆栈对象的列表。
	scanframe := func(frame *stkframe, unused unsafe.Pointer) bool {
		scanframeworker(frame, &state, gcw)
		return true
	}
	gentraceback(^uintptr(0), ^uintptr(0), 0, gp, 0, nil, 0x7fffffff, scanframe, nil, 0)

	// 从堆中查找指向堆栈的其他指针。
	// 目前这包括延期和恐慌。另请参见函数copystack。

	// 在延迟记录中查找并跟踪其他指针。
	for d := gp._defer; d != nil; d = d.link {
		if d.fn != nil {
			// 扫描func值，该值可能是堆栈分配的闭包。
			// 见第30453期。
			scanblock(uintptr(unsafe.Pointer(&d.fn)), goarch.PtrSize, &oneptrmask[0], gcw, &state)
		}
		if d.link != nil {
			// 堆栈分配的延迟记录的链接字段可能会将
			// 指向堆分配的延迟记录。保持那堆记录的实时性。
			scanblock(uintptr(unsafe.Pointer(&d.link)), goarch.PtrSize, &oneptrmask[0], gcw, &state)
		}
		// 保留延迟记录。
		// 延迟记录可能无法通过常规堆从G访问
		// 跟踪，因为延迟链表可能在堆栈和堆之间穿行。
		if d.heap {
			scanblock(uintptr(unsafe.Pointer(&d)), goarch.PtrSize, &oneptrmask[0], gcw, &state)
		}
	}
	if gp._panic != nil {
		// 内存始终是堆栈分配的。
		state.putPtr(uintptr(unsafe.Pointer(gp._panic)), false)
	}

	// 查找并扫描所有可访问的堆栈对象。
	// 
	// 该州的指针队列优先于
	// 保守指针，因此我们更喜欢精确扫描堆栈
	// 对象。
	state.buildIndex()
	for {
		p, conservative := state.getPtr()
		if p == 0 {
			break
		}
		obj := state.findObject(p)
		if obj == nil {
			continue
		}
		r := obj.r
		if r == nil {
			// 我们已经扫描了这个物体。
			continue
		}
		obj.setRecord(nil) // 不要再扫描它。
		if stackTraceDebug {
			printlock()
			print("  live stkobj at", hex(state.stack.lo+uintptr(obj.off)), "of size", obj.size)
			if conservative {
				print(" (conservative)")
			}
			println()
			printunlock()
		}
		gcdata := r.gcdata()
		var s *mspan
		if r.useGCProg() {
			// 这个路径不太可能，一个足够大的对象
			// 在堆栈上分配一个GC程序。
			// 我们需要一些空间将程序解压成一个直接的
			// 位掩码，我们在这里分配/释放它。
			// TODO:如果有一种方法可以运行GC 
			// 程序，而不必存储所有位，那就太好了。我们需要
			// 从Lempel-Ziv风格的程序转变为其他程序。
			// 或者如果需要
			// 一个gc程序（见第27447期）。
			s = materializeGCProg(r.ptrdata(), gcdata)
			gcdata = (*byte)(unsafe.Pointer(s.startAddr))
		}

		b := state.stack.lo + uintptr(obj.off)
		if conservative {
			scanConservative(b, r.ptrdata(), gcdata, gcw, &state)
		} else {
			scanblock(b, r.ptrdata(), gcdata, gcw, &state)
		}

		if s != nil {
			dematerializeGCProg(s)
		}
	}

	// 释放对象缓冲区。
	// （指针缓冲区都在上面的循环中释放。）
	for state.head != nil {
		x := state.head
		state.head = x.next
		if stackTraceDebug {
			for i := 0; i < x.nobj; i++ {
				obj := &x.obj[i]
				if obj.r == nil { // 可到达
					continue
				}
				println("  dead stkobj at", hex(gp.stack.lo+uintptr(obj.off)), "of size", obj.r.size)
				// 注意：不一定真的死了-只能从ptr死了才能到达。
			}
		}
		x.nobj = 0
		putempty((*workbuf)(unsafe.Pointer(x)))
	}
	if state.buf != nil || state.cbuf != nil || state.freeBuf != nil {
		throw("remaining pointer buffers")
	}
	return int64(stackSize)
}

// 扫描堆栈帧：局部变量和函数参数/结果。
// go:nowritebarrier 
func scanframeworker(frame *stkframe, state *stackScanState, gcw *gcWork) {
	if _DebugGC > 1 && frame.continpc != 0 {
		print("scanframe ", funcname(frame.fn), "\n")
	}

	isAsyncPreempt := frame.fn.valid() && frame.fn.funcID == funcID_asyncPreempt
	isDebugCall := frame.fn.valid() && frame.fn.funcID == funcID_debugCallV2
	if state.conservative || isAsyncPreempt || isDebugCall {
		if debugScanConservative {
			println("conservatively scanning function", funcname(frame.fn), "at PC", hex(frame.continpc))
		}

		// 保守地扫描帧。与精确的
		// 不同，这包括传出参数空间
		// 因为我们可能在函数
		// 设置调用时停止。
		// 
		// TODO:如果编译器
		// 为堆栈插槽
		// 和包含指针的寄存器的每个函数生成一个映射，我们可以缩小范围。
		if frame.varp != 0 {
			size := frame.varp - frame.sp
			if size > 0 {
				scanConservative(frame.sp, size, nil, gcw, state)
			}
		}

		// 扫描此帧的参数。
		if frame.arglen != 0 {
			// TODO:我们可以传递入口参数映射
			// 来进一步缩小范围。
			scanConservative(frame.argp, frame.arglen, nil, gcw, state)
		}

		if isAsyncPreempt || isDebugCall {
			// 此函数的帧包含异步停止的
			// 父帧的寄存器。保守地扫描父
			// 。我们只想保守地扫描这两帧。清除未来
			state.conservative = true
		} else {
			// 帧的标志。
			state.conservative = false
		}
		return
	}

	locals, args, objs := getStackMap(frame, &state.cache, false)

	// 如果已分配堆栈帧，则扫描局部变量。
	if locals.n > 0 {
		size := uintptr(locals.n) * goarch.PtrSize
		scanblock(frame.varp-size, size, locals.bytedata, gcw, state)
	}

	// 扫描参数。
	if args.n > 0 {
		scanblock(frame.argp, uintptr(args.n)*goarch.PtrSize, args.bytedata, gcw, state)
	}

	// 将所有堆栈对象添加到堆栈对象列表中。
	if frame.varp != 0 {
		// varp为0表示延迟，在没有本地人的情况下。
		// 在这种情况下，也不能有指向其参数的指针。
		// （所有参数都将在上面进行扫描。）
		for i := range objs {
			obj := &objs[i]
			off := obj.off
			base := frame.varp // 局部变量基指针
			if off >= 0 {
				base = frame.argp // 参数和返回值基指针
			}
			ptr := base + uintptr(off)
			if ptr < frame.sp {
				// 对象尚未在帧中分配。
				continue
			}
			if stackTraceDebug {
				println("stkobj at", hex(ptr), "of size", obj.size)
			}
			state.addObject(ptr, obj)
		}
	}
}

type gcDrainFlags int

const (
	gcDrainUntilPreempt gcDrainFlags = 1 << iota
	gcDrainFlushBgCredit
	gcDrainIdle
	gcDrainFractional
)

// gcDrain扫描工作缓冲区中的根和对象，将灰色
// 对象变黑，直到无法获得更多工作。它可能在
// GC完成之前返回；呼叫方有责任平衡来自
// 其他Ps的工作。
// 
// 如果标志和GCDRAINUNTILPREMPT！=0，当设置g.preempt 
// 时，gcDrain返回。
// 
// If flags&gcDrainIdle！=0，当有其他工作
// 要做时，gcDrain返回。
// 
// If flags&gcDrainFractional！=0，当
// pollFractIlWorkerExit（）返回true时，gcDrain自动抢占。这意味着
// GCDRAINOBLOCK。
// 
// If flags&gcDrainFlushBgCredit！=0，gcDrain刷新扫描工作
// 记入gcController。bgScanCredit 
// 扫描工作。
// 
// gcDrain将始终返回挂起的STW。
// 
// go:nowritebarrier 
func gcDrain(gcw *gcWork, flags gcDrainFlags) {
	if !writeBarrier.needed {
		throw("gcDrain phase incorrect")
	}

	gp := getg().m.curg
	preemptible := flags&gcDrainUntilPreempt != 0
	flushBgCredit := flags&gcDrainFlushBgCredit != 0
	idle := flags&gcDrainIdle != 0

	initScanWork := gcw.heapScanWork

	// 检查工作是执行下一个
	// 自抢占检查之前的扫描工作。
	checkWork := int64(1<<63 - 1)
	var check func() bool
	if flags&(gcDrainIdle|gcDrainFractional) != 0 {
		checkWork = initScanWork + drainCheckThreshold
		if idle {
			check = pollWork
		} else if flags&gcDrainFractional != 0 {
			check = pollFractionalWorkerExit
		}
	}

	// 排放根标记作业。
	if work.markrootNext < work.markrootJobs {
		// 如果我们有先发制人的能力，或者如果有人想要STW，请停止。
		for !(gp.preempt && (preemptible || atomic.Load(&sched.gcwaiting) != 0)) {
			job := atomic.Xadd(&work.markrootNext, +1) - 1
			if job >= work.markrootJobs {
				break
			}
			markroot(gcw, job, flushBgCredit)
			if check != nil && check() {
				goto done
			}
		}
	}

	// 漏堆标记作业。
	// 如果我们有先发制人的能力，或者如果有人想要STW，请停止。
	for !(gp.preempt && (preemptible || atomic.Load(&sched.gcwaiting) != 0)) {
		// 尽量保持全局队列上的工作可用。我们过去经常检查是否有等待的工人，但最好保持工作可用，而不是让工人等待。在
		// 最坏的情况下，我们将执行O（log（_WorkbufSize））不必要的
		// 余额。
		if work.full == 0 {
			gcw.balance()
		}

		b := gcw.tryGetFast()
		if b == 0 {
			b = gcw.tryGet()
			if b == 0 {
				// 刷新写屏障
				// 缓冲区；这可能会产生更多的工作。
				wbBufFlush(nil, 0)
				b = gcw.tryGet()
			}
		}
		if b == 0 {
			// 无法获得工作。
			break
		}
		scanobject(b, gcw)

		// 如果我们已经在本地积累了足够的资金，那么
		// 帐户。
		// mutator assists可以利用这笔资金，则可以将后台扫描工作贷记到全球
		if gcw.heapScanWork >= gcCreditSlack {
			gcController.heapScanWork.Add(gcw.heapScanWork)
			if flushBgCredit {
				gcFlushBgCredit(gcw.heapScanWork - initScanWork)
				initScanWork = 0
			}
			checkWork -= gcw.heapScanWork
			gcw.heapScanWork = 0

			if checkWork <= 0 {
				checkWork += drainCheckThreshold
				if check != nil && check() {
					break
				}
			}
		}
	}

done:
	// 刷新剩余扫描工作信用。
	if gcw.heapScanWork > 0 {
		gcController.heapScanWork.Add(gcw.heapScanWork)
		if flushBgCredit {
			gcFlushBgCredit(gcw.heapScanWork - initScanWork)
		}
		gcw.heapScanWork = 0
	}
}

// gcDrainN会将灰色对象变黑，直到它大致完成了
// 扫描工作单元或G被抢占。这是
// 尽力而为，因此如果无法获得工作
// 缓冲区，它可能会执行较少的工作。否则，它将执行至少n个工作单元，但
// 可能会执行更多，因为扫描总是以整个对象的增量进行。它返回执行的扫描工作量。
// 
// 调用方goroutine必须处于可抢占状态（例如，
// /_Gwaiting），以防止堆栈扫描期间出现死锁。作为
// 因此，必须在系统堆栈上调用它。
// 
// go:nowritebarrier 
// go:systemstack 
func gcDrainN(gcw *gcWork, scanWork int64) int64 {
	if !writeBarrier.needed {
		throw("gcDrainN phase incorrect")
	}

	// 可能已经在gcw上进行了扫描工作，我们不
	// 希望声明此调用已完成。
	workFlushed := -gcw.heapScanWork

	gp := getg().m.curg
	for !gp.preempt && workFlushed+gcw.heapScanWork < scanWork {
		// 参见gcDrain评论。
		if work.full == 0 {
			gcw.balance()
		}

		b := gcw.tryGetFast()
		if b == 0 {
			b = gcw.tryGet()
			if b == 0 {
				// 刷新写屏障缓冲区；
				// 这可能会产生更多的工作。
				wbBufFlush(nil, 0)
				b = gcw.tryGet()
			}
		}

		if b == 0 {
			// 尝试执行根作业。
			if work.markrootNext < work.markrootJobs {
				job := atomic.Xadd(&work.markrootNext, +1) - 1
				if job < work.markrootJobs {
					work := markroot(gcw, job, false)
					if goexperiment.PacerRedesign {
						workFlushed += work
					}
					continue
				}
			}
			// 没有堆或根作业。
			break
		}

		scanobject(b, gcw)

		// 刷新后台扫描工作学分。
		if gcw.heapScanWork >= gcCreditSlack {
			gcController.heapScanWork.Add(gcw.heapScanWork)
			workFlushed += gcw.heapScanWork
			gcw.heapScanWork = 0
		}
	}

	// 与gcDrain不同，这里不需要刷新剩余的工作
	// 因为这不会刷新到bgScanCredit和
	// gcw。dispose会将任何剩余工作冲洗到scanWork。

	return workFlushed + gcw.heapScanWork
}

// scanblock像scanobject一样扫描b，但使用显式
// 指针位图而不是堆位图。
// 
// 这用于扫描非堆根，因此不会更新
// gcw。字节标记或gcw。堆可以工作。
// 
// If stk！=无，可能的堆栈指针也会报告给stk.putPtr。
// go:nowritebarrier 
func scanblock(b0, n0 uintptr, ptrmask *uint8, gcw *gcWork, stk *stackScanState) {
	// 使用原始参数的本地副本，这样，由于以下一次抛出，堆栈跟踪
	// 将显示原始块
	// 基和范围。
	b := b0
	n := n0

	for i := uintptr(0); i < n; {
		// 查找下一个单词的位。
		bits := uint32(*addb(ptrmask, i/(goarch.PtrSize*8)))
		if bits == 0 {
			i += goarch.PtrSize * 8
			continue
		}
		for j := 0; j < 8 && i < n; j++ {
			if bits&1 != 0 {
				// 与scanobject中的工作相同；请参见此处的评论。
				p := *(*uintptr)(unsafe.Pointer(b + i))
				if p != 0 {
					if obj, span, objIndex := findObject(p, b, i); obj != 0 {
						greyobject(obj, b, i, span, gcw, objIndex)
					} else if stk != nil && p >= stk.stack.lo && p < stk.stack.hi {
						stk.putPtr(p, false)
					}
				}
			}
			bits >>= 1
			i += goarch.PtrSize
		}
	}
}

// scanobject从b开始扫描对象，向gcw添加指针。
// b必须指向堆对象或小对象的开头。
// scanobject为指针掩码参考GC位图，为对象大小参考
// spans。
// 
// go:nowritebarrier 
func scanobject(b uintptr, gcw *gcWork) {
	// 在扫描对象之前对其进行预取。
	// 
	// 在开始扫描对象之前，这将与初始
	// 设置重叠获取对象的开头。
	sys.Prefetch(b)

	// 查找b的位和b处对象的大小。
	// 
	// b是对象的开头，在这种情况下，这个
	// 是要扫描的对象的大小，或者它指向
	// oblet，在这种情况下，我们计算下面要扫描的大小。
	hbits := heapBitsForAddr(b)
	s := spanOfUnchecked(b)
	n := s.elemsize
	if n == 0 {
		throw("scanobject n == 0")
	}

	if n > maxObletBytes {
		// 大型对象。为了获得更好的
		// 并行性和更低的延迟，可以拆分成小对象。
		if b == s.base() {
			// 这可能是一个noscan对象（不是来自greyobject的
			// 而是来自其他代码
			// 路径），在这种情况下，我们必须*不*将
			// 对象排队，因为它们的位图将是
			// 未初始化的。
			if s.spanclass.noscan() {
				// 绕过整个扫描。
				gcw.bytesMarked += uint64(n)
				return
			}

			// 将其他OBlet排队等待稍后扫描。
			// 一些小对象可能在b的标量尾部，但
			// 这些小对象将被标记为“不再有指针”，
			// 所以当我们转到
			// 扫描这些小对象时，我们将立即退出。
			for oblet := b + maxObletBytes; oblet < s.base()+s.elemsize; oblet += maxObletBytes {
				if !gcw.putFast(oblet) {
					gcw.put(oblet)
				}
			}
		}

		// 计算小对象的大小。由于这个对象
		// 必须是一个大对象，因此s.base（）是该对象的开头
		// 。
		n = s.base() + s.elemsize - b
		if n > maxObletBytes {
			n = maxObletBytes
		}
	}

	var i uintptr
	for i = 0; i < n; i, hbits = i+goarch.PtrSize, hbits.next() {
		// 加载位一次。有关讨论，请参见第22712条和第16973期。
		bits := hbits.bits()
		if bits&bitScan == 0 {
			break // 此对象中不再有指针
		}
		if bits&bitPointer == 0 {
			continue // 不是指针
		}

		// 此处的工作在scanblock及以上版本中重复。
		// 如果你在这里做更改，也在那里做更改。
		obj := *(*uintptr)(unsafe.Pointer(b + i))

		// 此时，我们提取了下一个潜在指针。
		// 快速过滤掉nil并返回到当前对象。
		if obj != 0 && obj-b >= n {
			// 测试obj是否指向Go堆，如果是，
			// 标记对象。
			// 
			// 请注意，如果obj指向刚分配的堆
			// 可能会失败，因为正在与
			// 对象，则findObject to 
			// 堆进行竞争。在本例中，我们知道对象是刚刚分配的
			// 因此将被
			// 分配本身标记。
			if obj, span, objIndex := findObject(obj, b, i); obj != 0 {
				greyobject(obj, b, i, span, gcw, objIndex)
			}
		}
	}
	gcw.bytesMarked += uint64(n)
	gcw.heapScanWork += int64(i)
}

// 扫描保守扫描块[b，b+n]，将块中的任何
// 类似指针的值视为指针。
// 
// 如果ptrmak！=nil，则只有在ptrmak中标记的字才被视为潜在指针。
// 
// 如果state！=nil，则假定[b，b+n）是堆栈中的一个块
// 可能包含指向堆栈对象的指针。
func scanConservative(b, n uintptr, ptrmask *uint8, gcw *gcWork, state *stackScanState) {
	if debugScanConservative {
		printlock()
		print("conservatively scanning [", hex(b), ",", hex(b+n), ")\n")
		hexdumpWords(b, b+n, func(p uintptr) byte {
			if ptrmask != nil {
				word := (p - b) / goarch.PtrSize
				bits := *addb(ptrmask, word/8)
				if (bits>>(word%8))&1 == 0 {
					return '$'
				}
			}

			val := *(*uintptr)(unsafe.Pointer(p))
			if state != nil && state.stack.lo <= val && val < state.stack.hi {
				return '@'
			}

			span := spanOfHeap(val)
			if span == nil {
				return ' '
			}
			idx := span.objIndex(val)
			if span.isFree(idx) {
				return ' '
			}
			return '*'
		})
		printunlock()
	}

	for i := uintptr(0); i < n; i += goarch.PtrSize {
		if ptrmask != nil {
			word := i / goarch.PtrSize
			bits := *addb(ptrmask, word/8)
			if bits == 0 {
				// 跳过8个字（循环增量将执行第8个）
				// 
				// 这一定是我们第一次看到ptrmak这个词，所以我
				// 必须是8字对齐的，但请检查
				// 我们的推理以防万一。
				if i%(goarch.PtrSize*8) != 0 {
					throw("misaligned mask")
				}
				i += goarch.PtrSize*8 - goarch.PtrSize
				continue
			}
			if (bits>>(word%8))&1 == 0 {
				continue
			}
		}

		val := *(*uintptr)(unsafe.Pointer(b + i))

		// 检查val是否指向堆栈。
		if state != nil && state.stack.lo <= val && val < state.stack.hi {
			// val可能指向堆栈对象。这个
			// 对象可能在上一个循环中已经死了，
			// 因此可能包含指向未分配
			// 对象的指针，但与堆对象不同，我们无法判断它是否已经死了。因此，如果所有指向此对象的指针都来自保守扫描，那么我们也必须对其进行防御扫描。
			state.putPtr(val, true)
			continue
		}

		// 检查val是否指向堆范围。
		span := spanOfHeap(val)
		if span == nil {
			continue
		}

		// 检查val是否指向分配的对象。
		idx := span.objIndex(val)
		if span.isFree(idx) {
			continue
		}

		// val指向已分配的对象。记住。
		obj := span.base() + idx*span.elemsize
		greyobject(obj, b, i, span, gcw, idx)
	}
}

// 如果对象尚未着色，则对其进行着色。
// 对象不是nil，并且已知在堆中。
// 必须禁用抢占。
// go:nowritebarrier 
func shade(b uintptr) {
	if obj, span, objIndex := findObject(b, 0, 0); obj != 0 {
		gcw := &getg().m.p.ptr().gcw
		greyobject(obj, 0, 0, span, gcw, objIndex)
	}
}

// obj是带有标记mbits的对象的开头。
// 如果尚未标记，请将其标记并排队进入gcw。
// base和off仅用于调试，可以删除。
// 
// 另请参见wbBufFlush1，它部分复制了此逻辑。
// 
// go:nowritebarrierrec 
func greyobject(obj, base, off uintptr, span *mspan, gcw *gcWork, objIndex uintptr) {
	// obj应该是分配的开始，因此必须至少与指针对齐。
	if obj&(goarch.PtrSize-1) != 0 {
		throw("greyobject: obj not pointer-aligned")
	}
	mbits := span.markBitsForIndex(objIndex)

	if useCheckmark {
		if setCheckmark(obj, base, off, mbits) {
			// 已标记。
			return
		}
	} else {
		if debug.gccheckmark > 0 && span.isFree(objIndex) {
			print("runtime: marking free object ", hex(obj), " found at *(", hex(base), "+", hex(off), ")\n")
			gcDumpObject("base", base, off)
			gcDumpObject("obj", obj, ^uintptr(0))
			getg().m.traceback = 2
			throw("marking free object")
		}

		// 如果被标记，我们将无事可做。
		if mbits.isMarked() {
			return
		}
		mbits.setMarked()

		// 标记span。如果这是一个noscan对象，请将其快速跟踪到黑色，而不是变灰。
		arena, pageIdx, pageMask := pageIndexOf(span.base())
		if arena.pageMarks[pageIdx]&pageMask == 0 {
			atomic.Or8(&arena.pageMarks[pageIdx], pageMask)
		}

		if span.spanclass.noscan() {
			gcw.bytesMarked += uint64(span.elemsize)
			return
		}
	}

	// 我们正在将obj添加到P的本地workbuf中，因此很可能
	// 这个对象很快将由同一个P处理。
	// 即使workbuf被刷新，在具有包容性共享缓存的平台上也可能会有一些好处。
	sys.Prefetch(obj)
	// 将obj排队进行扫描。
	if !gcw.putFast(obj) {
		gcw.put(obj)
	}
}

// gcDumpObject转储obj的内容进行调试，并在obj中字节偏移量off处标记
// 字段。
func gcDumpObject(label string, obj, off uintptr) {
	s := spanOf(obj)
	print(label, "=", hex(obj))
	if s == nil {
		print(" s=nil\n")
		return
	}
	print(" s.base()=", hex(s.base()), " s.limit=", hex(s.limit), " s.spanclass=", s.spanclass, " s.elemsize=", s.elemsize, " s.state=")
	if state := s.state.get(); 0 <= state && int(state) < len(mSpanStateNames) {
		print(mSpanStateNames[state], "\n")
	} else {
		print("unknown(", state, ")\n")
	}

	skipped := false
	size := s.elemsize
	if s.state.get() == mSpanManual && size == 0 {
		// 我们正在从堆栈框架打印一些东西。我们不知道它有多大，所以只要去看一个
		// 包括关闭。
		size = off + goarch.PtrSize
	}
	for i := uintptr(0); i < size; i += goarch.PtrSize {
		// 对于大对象，只需打印开头（因为
		// 通常会提示对象的类型）和周围的
		// 字段。
		if !(i < 128*goarch.PtrSize || off-16*goarch.PtrSize < i && i < off+16*goarch.PtrSize) {
			skipped = true
			continue
		}
		if skipped {
			print(" ...\n")
			skipped = false
		}
		print(" *(", label, "+", i, ") = ", hex(*(*uintptr)(unsafe.Pointer(obj + i))))
		if i == off {
			print(" <==")
		}
		print("\n")
	}
	if skipped {
		print(" ...\n")
	}
}

// gcmarknewobject将新分配的对象标记为黑色。obj必须
// 不包含任何非零指针。
// 
// 这是nosplit，因此它可以在不抢占的情况下操纵gcWork。
// 
// go:nowritebarrier 
// go:nosplit 
func gcmarknewobject(span *mspan, obj, size, scanSize uintptr) {
	if useCheckmark { // 世界应该停止，这样就不会发生这种情况。
		throw("gcmarknewobject called while doing checkmark")
	}

	// 标记对象。
	objIndex := span.objIndex(obj)
	span.markBitsForIndex(objIndex).setMarked()

	// 马克·斯潘。
	arena, pageIdx, pageMask := pageIndexOf(span.base())
	if arena.pageMarks[pageIdx]&pageMask == 0 {
		atomic.Or8(&arena.pageMarks[pageIdx], pageMask)
	}

	gcw := &getg().m.p.ptr().gcw
	gcw.bytesMarked += uint64(size)
	if !goexperiment.PacerRedesign {
		// 旧的pacer将新分配的内存计数为
		// heapScanWork，因为heapScan在GC循环的整个过程中都在不断更新
		// 新分配的内存。然而，
		// 这些对象从未被实际扫描过，所以我们需要
		// 在这里的堆扫描中解释它们，“伪造”它们的工作。
		// 否则步行者会认为自己总是落后，可能会以很大的优势落后。
		// 
		// 新的pacer不在乎这一点，因为一旦GC循环开始，它就会停止更新
		// heapScan，有效地对其进行快照。
		gcw.heapScanWork += int64(scanSize)
	}
}

// gcMarkTinyAllocs灰显所有活跃的微小alloc块。世界必须被阻止。
func gcMarkTinyAllocs() {
	assertWorldStopped()

	for _, p := range allp {
		c := p.mcache
		if c == nil || c.tiny == 0 {
			continue
		}
		_, span, objIndex := findObject(c.tiny, 0, 0)
		gcw := &p.gcw
		greyobject(c.tiny, 0, 0, span, gcw, objIndex)
	}
}
